home *** CD-ROM | disk | FTP | other *** search
/ CD ROM Paradise Collection 4 / CD ROM Paradise Collection 4 1995 Nov.iso / music / c2snd201.zip / SND2WAV.PAS < prev   
Pascal/Delphi Source File  |  1994-09-11  |  34KB  |  898 lines

  1. (* Snd2wav.pas - Convert DeskMate .snd to RIFF WAVE, version 1.1
  2.    Jeffrey L. Hayes
  3.    September 11, 1994
  4.  
  5.    This is a companion program to Kenneth Udut's Conv2snd program for 
  6.    converting .wav and other formats to .snd.  I did the revisions for 
  7.    version 2.00 of that program.
  8.  
  9.    This program converts DeskMate .snd files to RIFF WAVE format, allowing 
  10.    them to be played without the DeskMate Sound.pdm program.  This allows 
  11.    sounds recorded with Sound.pdm to be shared with users on non-Tandy 
  12.    machines.
  13.  
  14.    .snd files can be compressed; this program can only convert the 
  15.    uncompressed kind.  To convert compressed .snd's, Sound.pdm will first 
  16.    have to be used to decompress them.
  17.  
  18.    .snd files are of two basic types, "sound" files and "instrument" files.  
  19.    Both contain 8-bit unsigned PCM samples, but instrument files may 
  20.    contain more than one sample and may have looping information.  Sound 
  21.    files can be converted directly to .wav with no special treatment.  
  22.  
  23.    Instrument files reproduce the sound of musical instruments for use with 
  24.    Music.pdm.  Outside the DeskMate environment, they are of limited use.  
  25.    The only sensible thing I can think of to do with them is to make them 
  26.    into .mod samples, but this program just turns each note into a separate 
  27.    .wav file, ignoring the note, the range, and the sustain region.  (I 
  28.    have written another program, Snd2sam, that will convert instrument 
  29.    files to .mod samples - see Snd2wav.doc.)
  30.  
  31.    Snd2wav can take one or two command-line parameters, though none is 
  32.    required.  If the program is invoked without parameters, the user will 
  33.    be prompted for the input filename.  If a single parameter is given, it 
  34.    will be taken as the input filename.  In either of these cases, the 
  35.    output filename will default to the same drive and path as the input 
  36.    file, but with a ".wav" extension.  If two command-line parameters are 
  37.    given, the second parameter is taken as the output filename.  If this 
  38.    name has no extension, a ".wav" extension is appended.  If the input 
  39.    file has no extension, the extension defaults to ".snd".  If the user 
  40.    desires to use a filename without an extension, he can specify a name 
  41.    ending in a period.
  42.  
  43.    If an instrument file with more than one note is being converted, a 
  44.    digit 2-9 or letter A-G is appended to the filename for the second and 
  45.    subsequent notes, overwriting the last character of the filename if 
  46.    necessary.  For example, if the following is entered:
  47.  
  48.        snd2wav piano
  49.  
  50.    and piano.snd is an instrument file with 3 notes, the following files 
  51.    are created:
  52.  
  53.        piano.wav
  54.        piano2.wav
  55.        piano3.wav
  56.  
  57.    Snd2wav returns errorlevel 1 if conversion fails for whatever reason; 
  58.    otherwise it returns errorlevel 0.  Note that converting an empty .snd 
  59.    file produces no output files, but still returns errorlevel 0.
  60. *)
  61.  
  62. (*********************************************************************)
  63. (*********************************************************************)
  64.  
  65. program snd2wav;
  66.  
  67. type
  68.   noterec = record         (* needed fields from the .snd note record *)
  69.       start: longint;      (* starting offset in the file for note samples *)
  70.       length: longint;     (* number of note samples *)
  71.     end; (* record *)
  72.   notearray = array [1..16] of noterec;
  73.   string3 = string[3];     (* string type for file extensions *)
  74.  
  75. var
  76.   sndfile: file;           (* input untyped file, treat as byte stream *)
  77.   numnotes: byte;          (* number of notes in the .snd file *)
  78.   rate: word;              (* sampling rate in samples per second *)
  79.   note: byte;              (* current note in the .snd file being converted *)
  80.   notelist: notearray;     (* list of notes in the .snd file *)
  81.   sndname: string;         (* name of input .snd file, including path *)
  82.   wavname: string;         (* name of output .wav file, including path *)
  83.  
  84. (*********************************************************************)
  85.  
  86. function lastpos(
  87.     st: string;            (* string to be searched *)
  88.     ch: char):             (* character to search for *)
  89.       integer;
  90.   (* Returns the position of the last occurrence of ch in st, 0 if not 
  91.      present. *)
  92.  
  93. var
  94.   i: integer;           (* for looping over the characters *)
  95.   place: integer;       (* place where ch is found *)
  96.  
  97. begin (* lastpos *)
  98.   i := length( st );
  99.   place := 0;
  100.   while (i > 0) and (place = 0) do
  101.     begin
  102.     if st[i] = ch then
  103.       place := i;
  104.     i := i - 1;
  105.     end; (* while *)
  106.   lastpos := place;
  107. end; (* lastpos *)
  108.  
  109. (*********************************************************************)
  110.  
  111. function has_extension(
  112.     st: string ):       (* filename to test *)
  113.       boolean;
  114.   (* This function returns true if the string st has a file extension. *)
  115.  
  116. var
  117.   dotplace: integer;       (* last position of '.' in st *)
  118.   slashplace: integer;     (* last position of '\' in st *)
  119.   colonplace: integer;     (* last position of ':' in st *)
  120.  
  121. begin (* has_extension *)
  122.   slashplace := lastpos( st, '\' );
  123.   colonplace := lastpos( st, ':' );
  124.   if colonplace > slashplace then
  125.     slashplace := colonplace;
  126.   if slashplace <> 0 then
  127.     delete( st, 1, slashplace );
  128.   dotplace := lastpos( st, '.' );
  129.   if dotplace = 0 then
  130.     has_extension := False
  131.   else
  132.     has_extension := (dotplace >= length( st )-3);
  133. end; (* has_extension *)
  134.  
  135. (*********************************************************************)
  136.  
  137. function set_extension(
  138.     st: string;         (* filename whose extension is to be changed *)
  139.     ext: string3 ):     (* extension to replace the current one *)
  140.       string;
  141.   (* This function returns the input filename st after replacing its 
  142.      extension with ext. *)
  143.  
  144. var
  145.   dotplace: integer;       (* last position of '.' in st *)
  146.   slashplace: integer;     (* last position of '\' in st *)
  147.   colonplace: integer;     (* last position of ':' in st *)
  148.   pathname: string;        (* drive and path, excluding filename *)
  149.   filename: string;        (* filename, excluding drive and path *)
  150.  
  151. begin (* set_extension *)
  152.   slashplace := lastpos( st, '\' );
  153.   colonplace := lastpos( st, ':' );
  154.   if colonplace > slashplace then
  155.     slashplace := colonplace;
  156.   if slashplace = 0 then
  157.     pathname := ''
  158.   else
  159.     begin
  160.     pathname := copy( st, 1, slashplace );
  161.     delete( st, 1, slashplace );
  162.     end; (* else *)
  163.   filename := st;
  164.   dotplace := lastpos( filename, '.' );
  165.   if dotplace = 0 then
  166.     filename := filename + '.' + ext
  167.   else
  168.     filename := copy( filename, 1, dotplace ) + ext;
  169.   set_extension := pathname + filename;
  170. end; (* set_extension *)
  171.  
  172. (*********************************************************************)
  173.  
  174. procedure display_intro;
  175.   (* This procedure displays an introductory message on the screen. *)
  176.  
  177. begin (* display_intro *)
  178.   writeln;
  179.   writeln( 'Snd2wav, v. 1.1:  DeskMate .snd to RIFF WAVE conversion ',
  180.     'program.' );
  181.   writeln;
  182. end; (* display_intro *)
  183.  
  184. (*********************************************************************)
  185.  
  186. procedure get_filenames(
  187.     var sndname:        (* name of input .snd file, returned *)
  188.       string;
  189.     var wavname:        (* name of output .wav file, returned *)
  190.       string );
  191.   (* This procedure reads the input and output filenames from the command
  192.      line, if present.  If the input filename is not present on the command
  193.      line, the user is prompted for it.  If the output filename is not
  194.      present on the command line, the output filename will default to the
  195.      same drive and path as the input filename, but with a ".wav"
  196.      extension.  If the output file is specified but has no extension, a
  197.      ".wav" extension will be appended. *)
  198.  
  199. begin (* get_filenames *)
  200.     (* more than 2 command-line parameters is invalid *)
  201.   if ParamCount > 2 then
  202.     begin
  203.     writeln( 'This program takes at most 2 filenames on the command ',
  204.       'line.  Use one of the' );
  205.     writeln( 'following forms:' );
  206.     writeln;
  207.     writeln( '  snd2wav' );
  208.     writeln( '  snd2wav <sndfile>' );
  209.     writeln( '  snd2wav <sndfile> <wavfile>' );
  210.     halt( 1 );
  211.     end; (* if ParamCount > 2 *)
  212.  
  213.     (* if no command line parameters, get the input filename from the
  214.        user *)
  215.   if ParamCount = 0 then
  216.     begin
  217.     writeln( 'Please specify the .snd file to be converted (drive ',
  218.       'and/or path OK):' );
  219.     readln( sndname );
  220.     end (* if ParamCount = 0 *)
  221.   else
  222.     sndname := ParamStr(1);
  223.  
  224.     (* if the input filename has no extension, append ".snd" *)
  225.   if not has_extension( sndname ) then
  226.     sndname := set_extension( sndname, 'snd' );
  227.  
  228.     (* if the output file is not specified, set it to the default *)
  229.   if ParamCount < 2 then
  230.     wavname := set_extension( sndname, 'wav' )
  231.   else
  232.     begin
  233.     wavname := ParamStr(2);
  234.     if not has_extension( wavname ) then
  235.       wavname := set_extension( wavname, 'wav' );
  236.     end; (* else if ParamCount = 2 *)
  237. end; (* get_filenames *)
  238.  
  239. (*********************************************************************)
  240.  
  241. function is_newsnd(
  242.   sndname:                   (* name in input file *)
  243.     string ):
  244.       boolean;
  245.   (*  This function returns true if the input file is a new-format .snd 
  246.       file, or at least _not_ an old-format .snd file.  *)
  247.  
  248. var
  249.   sndfile:                   (* input file *)
  250.     file;
  251.   firstbyte:                 (* first byte of the file *)
  252.     byte;
  253.   IDtag:                     (* ID tag for new .snd file *)
  254.     array [0..1] of byte;
  255.   bytesread:                 (* number of bytes successfully read *)
  256.     word;
  257.  
  258. begin (* is_newsnd *)
  259.     (* open the input file *)
  260.   assign( sndfile, sndname );
  261.   {$I-} reset( sndfile, 1 ); {$I+}
  262.     (* Note:  For some bizarre reason, reset() fails on read-only files. *)
  263.     (*   I decided to live with it (... and make my users live with it). *)
  264.   if IOResult <> 0 then
  265.     begin
  266.     writeln( 'File ', sndname, ' does not exist.' );
  267.     writeln( 'Check the filename and try again.' );
  268.     halt( 1 );
  269.     end;
  270.  
  271.     (* if the file does not contain at least 46 bytes, it's not a new- 
  272.        format file (we verify the file size to keep from seeking or reading 
  273.        past the end of the file) *)
  274.   if filesize( sndfile ) < 46 then
  275.     begin
  276.     is_newsnd := false;
  277.     exit;
  278.     end;
  279.  
  280.     (* read the first byte of the file *)
  281.   blockread( sndfile, firstbyte, 1, bytesread );
  282.  
  283.     (* seek to the magic number *)
  284.   seek( sndfile, 44 );
  285.  
  286.     (* read the ID tag *)
  287.   blockread( sndfile, IDtag, 2, bytesread );
  288.  
  289.     (* close the input file *)
  290.   close( sndfile );
  291.  
  292.     (* return true if ID is a match *)
  293.   is_newsnd := (firstbyte <> $1A) and (IDtag[0] = $1A) and (IDtag[1] = $80);
  294. end; (* is_newsnd *)
  295.  
  296. (*********************************************************************)
  297.  
  298. procedure read_newheader(
  299.     var sndname:        (* name of input .snd file *)
  300.       string;
  301.     var sndfile:        (* input .snd file, opened by this procedure *)
  302.       file;
  303.     var rate:           (* sampling rate from .snd header, returned *)
  304.       word;
  305.     var numnotes:       (* number of notes in the .snd file, returned *)
  306.       byte;
  307.     var notelist:       (* list of notes in the .snd file, returned *)
  308.       notearray );
  309.   (* This procedure opens the input .snd file and reads the header.  If the 
  310.      input file cannot be opened, or if the input file is a compressed file, 
  311.      or if EOF is encountered while reading the header, the input file is 
  312.      closed (if open) and the program is halted with an error message.  
  313.      This routine is for the new-format .snd files used on the 2500-series. *)
  314.  
  315. var
  316.   open_successful:         (* true if input file successfully opened *)
  317.     boolean;
  318.   fixed_header:            (* fixed header from .snd file *)
  319.     array [0..113] of byte;
  320.   bytesread:               (* number of bytes successfully read from the *)
  321.     word;                  (*   input file                               *)
  322.   valid:                   (* true if the input file is valid so far *)
  323.     boolean;
  324.   wordptr:                 (* for extracting words from the input buffers *)
  325.     ^word;
  326.   longptr:                 (* for extracting longs from the input buffers *)
  327.     ^longint;
  328.   i:                       (* loop counter *)
  329.     integer;
  330.   note_header:             (* note record from .snd file *)
  331.     array [0..45] of byte;
  332.   nextnote:                (* offset in file of next note record *)
  333.     longint;
  334.  
  335. begin (* read_newheader *)
  336.     (* attempt to open the input .snd file *)
  337.   assign( sndfile, sndname );
  338.   {$I-}
  339.   reset( sndfile, 1 );
  340.   {$I+}
  341.   open_successful := (IOResult = 0);
  342.  
  343.     (* if unsuccessful, display an error message and halt the program *)
  344.   if not open_successful then
  345.     begin
  346.     writeln( 'File ', sndname, ' does not exist.' );
  347.     writeln( 'Check the filename and try again.' );
  348.     halt( 1 );
  349.     end;
  350.  
  351.     (* read in the fixed .snd header part *)
  352.   blockread( sndfile, fixed_header, 114, bytesread );
  353.  
  354.     (* verify the fixed header *)
  355.   valid := (bytesread = 114);
  356.   valid := valid and (fixed_header[$2C] = $1A);
  357.   valid := valid and (fixed_header[$2D] = $80);
  358.   valid := valid and (fixed_header[$42] <= 2);
  359.   valid := valid and (fixed_header[$2E] in [1..16]);
  360.  
  361.     (* if there was an error, display a message and halt *)
  362.   if not valid then
  363.     begin
  364.     writeln( 'You specified:  ', sndname );
  365.     writeln( '... as the input file.  It is not a valid .snd file.  Either ',
  366.       'the file is' );
  367.     writeln( 'corrupt, or you tried to convert the wrong file.  Check the ',
  368.       'filename and try' );
  369.     writeln( 'again if you mistyped.' );
  370.     close( sndfile );
  371.     halt( 1 );
  372.     end; (* if not valid *)
  373.  
  374.     (* if the file is compressed, display a message and halt *)
  375.   if fixed_header[$42] <> 0 then
  376.     begin
  377.     writeln( 'The input file you specified:  ', sndname );
  378.     writeln( '... is compressed.  Snd2wav can''t convert compressed ',
  379.       '.snd''s.  Load the file' );
  380.     writeln( 'into Sound.pdm, turn compression off, and resave the file.  ',
  381.       'Then try again.' );
  382.     close( sndfile );
  383.     halt( 1 );
  384.     end; (* if fixed_header[$42] <> 0 *)
  385.  
  386.     (* extract fixed header information *)
  387.   numnotes := fixed_header[$2E];
  388.   wordptr := @fixed_header[$58];
  389.   rate := wordptr^;
  390.  
  391.     (* Loop over the note records. *)
  392.   for i := 1 to numnotes do
  393.     begin
  394.  
  395.       (* read in a note record *)
  396.     blockread( sndfile, note_header, 46, bytesread );
  397.  
  398.       (* if EOF, halt the program with an error message *)
  399.     if bytesread <> 46 then
  400.       begin
  401.       writeln( 'You specified:  ', sndname );
  402.       writeln( '... as the input file.  It is not a valid .snd file.  ',
  403.         'Either the file is' );
  404.       writeln( 'corrupt, or you tried to convert the wrong file.  Check the ',
  405.         'filename and try' );
  406.       writeln( 'again if you mistyped.' );
  407.       close( sndfile );
  408.       halt( 1 );
  409.       end; (* if bytesread <> 46 *)
  410.  
  411.       (* extract information from note record *)
  412.     longptr := @note_header[$0A];
  413.     notelist[i].start := longptr^;
  414.     longptr := @note_header[$12];
  415.     notelist[i].length := longptr^;
  416.  
  417.       (* seek to the next note record *)
  418.     longptr := @note_header[0];
  419.     nextnote := longptr^;
  420.     if nextnote > filesize( sndfile ) then
  421.       begin
  422.       writeln( 'You specified:  ', sndname );
  423.       writeln( '... as the input file.  It is not a valid .snd file.  ',
  424.         'Either the file is' );
  425.       writeln( 'corrupt, or you tried to convert the wrong file.  Check the ',
  426.         'filename and try' );
  427.       writeln( 'again if you mistyped.' );
  428.       close( sndfile );
  429.       halt( 1 );
  430.       end; (* if nextnote > filesize( sndfile ) *)
  431.     seek( sndfile, nextnote );
  432.     end; (* for i := 1 to numnotes *)
  433. end; (* read_newheader *)
  434.  
  435. (*********************************************************************)
  436.  
  437. procedure read_sndheader(
  438.     var sndname:        (* name of input .snd file *)
  439.       string;
  440.     var sndfile:        (* input .snd file, opened by this procedure *)
  441.       file;
  442.     var rate:           (* sampling rate from .snd header, returned *)
  443.       word;
  444.     var numnotes:       (* number of notes in the .snd file, returned *)
  445.       byte;
  446.     var notelist:       (* list of notes in the .snd file, returned *)
  447.       notearray );
  448.   (* This procedure opens the input .snd file and reads the header.  If the 
  449.      input file cannot be opened, or if the input file is a compressed file, 
  450.      or if EOF is encountered while reading the header, the input file is 
  451.      closed (if open) and the program is halted with an error message. *)
  452.  
  453. var
  454.   open_successful:         (* true if input file successfully opened *)
  455.     boolean;
  456.   fixed_header:            (* fixed header from .snd file *)
  457.     array [0..15] of byte;
  458.   bytesread:               (* number of bytes successfully read from the *)
  459.     word;                  (*   input file                               *)
  460.   valid:                   (* true if the input file is valid so far *)
  461.     boolean;
  462.   wordptr:                 (* for extracting words from the input buffers *)
  463.     ^word;
  464.   longptr:                 (* for extracting longs from the input buffers *)
  465.     ^longint;
  466.   i:                       (* loop counter *)
  467.     integer;
  468.   note_header:             (* note record from .snd file *)
  469.     array [0..27] of byte;
  470.  
  471. begin (* read_sndheader *)
  472.     (* attempt to open the input .snd file *)
  473.   assign( sndfile, sndname );
  474.   {$I-}
  475.   reset( sndfile, 1 );
  476.   {$I+}
  477.   open_successful := (IOResult = 0);
  478.  
  479.     (* if unsuccessful, display an error message and halt the program *)
  480.   if not open_successful then
  481.     begin
  482.     writeln( 'File ', sndname, ' does not exist.' );
  483.     writeln( 'Check the filename and try again.' );
  484.     halt( 1 );
  485.     end;
  486.  
  487.     (* read in the fixed .snd header part *)
  488.   blockread( sndfile, fixed_header, 16, bytesread );
  489.  
  490.     (* verify the fixed header *)
  491.   valid := (bytesread = 16);
  492.   valid := valid and (fixed_header[0] = $1A);
  493.   valid := valid and (fixed_header[1] <= 2);
  494.   valid := valid and (fixed_header[2] in [1..16]);
  495.   valid := valid and (fixed_header[3] in [0..32, $FF]);
  496.  
  497.     (* if there was an error, display a message and halt *)
  498.   if not valid then
  499.     begin
  500.     writeln( 'You specified:  ', sndname );
  501.     writeln( '... as the input file.  It is not a valid .snd file.  Either ',
  502.       'the file is' );
  503.     writeln( 'corrupt, or you tried to convert the wrong file.  Check the ',
  504.       'filename and try' );
  505.     writeln( 'again if you mistyped.' );
  506.     close( sndfile );
  507.     halt( 1 );
  508.     end; (* if not valid *)
  509.  
  510.     (* if the file is compressed, display a message and halt *)
  511.   if fixed_header[1] <> 0 then
  512.     begin
  513.     writeln( 'The input file you specified:  ', sndname );
  514.     writeln( '... is compressed.  Snd2wav can''t convert compressed ',
  515.       '.snd''s.  Load the file' );
  516.     writeln( 'into Sound.pdm, turn compression off, and resave the file.  ',
  517.       'Then try again.' );
  518.     close( sndfile );
  519.     halt( 1 );
  520.     end; (* if fixed_header[1] <> 0 *)
  521.  
  522.     (* extract fixed header information *)
  523.   numnotes := fixed_header[2];
  524.   wordptr := @fixed_header[$0E];
  525.   rate := wordptr^;
  526.  
  527.     (* Loop over the note records. *)
  528.   for i := 1 to numnotes do
  529.     begin
  530.  
  531.       (* read in a note record *)
  532.     blockread( sndfile, note_header, 28, bytesread );
  533.  
  534.       (* if EOF, halt the program with an error message *)
  535.     if bytesread <> 28 then
  536.       begin
  537.       writeln( 'You specified:  ', sndname );
  538.       writeln( '... as the input file.  It is not a valid .snd file.  ',
  539.         'Either the file is' );
  540.       writeln( 'corrupt, or you tried to convert the wrong file.  Check the ',
  541.         'filename and try' );
  542.       writeln( 'again if you mistyped.' );
  543.       close( sndfile );
  544.       halt( 1 );
  545.       end; (* if bytesread <> 28 *)
  546.  
  547.       (* extract information from note record *)
  548.     longptr := @note_header[4];
  549.     notelist[i].start := longptr^;
  550.     longptr := @note_header[$10];
  551.     notelist[i].length := longptr^;
  552.     end; (* for i := 1 to numnotes *)
  553. end; (* read_sndheader *)
  554.  
  555. (*********************************************************************)
  556.  
  557. function set_last(
  558.      st: string;        (* filename to be modified *)
  559.      c: char ):         (* character to be appended to the filename *)
  560.         string;
  561.   (* This function takes a filename in st and a character in c.  If the 
  562.      filename (excluding drive, path, and extension) has fewer than 8 
  563.      characters, the function returns the filename with character c 
  564.      appended (and with the same drive, path, and extension).  If the 
  565.      filename is already 8 characters long, the last character in the 
  566.      filename is replaced by c in the string returned. *)
  567.  
  568. var
  569.   slashplace: integer;     (* position of last '\' in st *)
  570.   colonplace: integer;     (* position of last ':' in st *)
  571.   dotplace: integer;       (* position of last '.' in st *)
  572.   pathname: string;        (* drive and pathname of st *)
  573.   filename: string;        (* filename of st, excluding drive, path, and ext *)
  574.   ext: string;             (* extension of filename *)
  575.  
  576. begin (* set_last *)
  577.   slashplace := lastpos( st, '\');
  578.   colonplace := lastpos( st, ':');
  579.   if colonplace > slashplace then
  580.     slashplace := colonplace;
  581.   if slashplace = 0 then
  582.     pathname := ''
  583.   else
  584.     begin
  585.     pathname := copy( st, 1, slashplace );
  586.     delete( st, 1, slashplace );
  587.     end; (* else *)
  588.   dotplace := lastpos( st, '.' );
  589.   if dotplace = 0 then
  590.     begin
  591.     filename := st;
  592.     ext := '';
  593.     end (* if dotplace = 0 *)
  594.   else if dotplace = 1 then
  595.     begin
  596.     filename := '';
  597.     delete( st, 1, 1 );
  598.     ext := st;
  599.     end (* else if dotplace = 1 *)
  600.   else (* dotplace > 1 *)
  601.     begin
  602.     filename := copy( st, 1, dotplace-1 );
  603.     delete( st, 1, dotplace );
  604.     ext := st;
  605.     end; (* else if dotplace > 1 *)
  606.   if filename = '' then
  607.     set_last := ' '      (* for invalid names, so they won't be opened *)
  608.   else
  609.     begin
  610.     if length( filename ) = 8 then
  611.       filename[8] := c
  612.     else
  613.       filename := filename + c;
  614.     set_last := pathname + filename + '.' + ext;
  615.     end; (* else if filename <> '' *)
  616. end; (* set_last *)
  617.  
  618. (*********************************************************************)
  619.  
  620. function byte2char(
  621.     b: byte ):          (* byte to be converted *)
  622.       char;
  623.   (* This function converts byte integer b to an ASCII character.  If b is 
  624.      in the range 0..9, a digit '0'..'9' is returned.  Otherwise, if b is 
  625.      10 or more, an uppercase letter is returned starting with 'A'. *)
  626.  
  627. begin (* byte2char *)
  628.   if b < 10 then
  629.     byte2char := chr( b + ord( '0' ) )
  630.   else
  631.     byte2char := chr( b - 10 + ord( 'A' ) );
  632. end; (* byte2char *)
  633.  
  634. (*********************************************************************)
  635.  
  636. procedure convert_note(
  637.     var sndfile:        (* input .snd file *)
  638.       file;
  639.     wavname:            (* name of output .wav file, may be modified *)
  640.       string;
  641.     rate:               (* sampling rate *)
  642.       word;
  643.     note:               (* note in .snd file to be converted *)
  644.       byte;
  645.     notelist:           (* list of notes in the .snd file *)
  646.       notearray );
  647.   (* This routine extracts a single note from the input file and writes 
  648.      that note to a .wav file.  If the note is note 1, wavname is used 
  649.      unmodified as the output filename.  Otherwise, the note number is 
  650.      appended to the output filename, overwriting the last character of
  651.      wavname if the name is already 8 characters long.  For notes 10-16,
  652.      letters A-G will be used.  If there is an error reading from the input
  653.      file, or if a full disk is detected, both files are closed, the output
  654.      file is erased, and the program is halted with an error message. *)
  655.  
  656. const
  657.   bufsize = 2048;          (* size of the file I/O buffer *)
  658.  
  659. type
  660.   paoc4 =                  (* type for labels in the .wav header *)
  661.     packed array [0..3] of char;
  662.  
  663. var
  664.   wavfile:                 (* output .wav file for this note *)
  665.     file;
  666.   open_successful:         (* true if output file successfully opened *)
  667.     boolean;
  668.   valid:                   (* true if the start and length of the note data *)
  669.     boolean;               (*   from the notelist are valid                 *)
  670.   notestart:               (* starting offset of the note data in the .snd *)
  671.     longint;               (*   file, copied from notelist                 *)
  672.   notelength:              (* length of the note data in the .snd file, *)
  673.     longint;               (*   copied from notelist                    *)
  674.   wavheader:               (* header for new .wav file *)
  675.     array [0..43] of byte;
  676.   stptr:                   (* for filling in string fields in the header *)
  677.     ^paoc4;
  678.   wordptr:                 (* for filling in word fields in the header *)
  679.     ^word;
  680.   longptr:                 (* for filling in longint fields in the header *)
  681.     ^longint;
  682.   byteswritten:            (* number of bytes successfully written out *)
  683.     word;
  684.   buffer:                  (* file I/O buffer for copying samples *)
  685.     array [1..bufsize] of byte;
  686.   bytesleft:               (* number of bytes (samples) left to copy *)
  687.     longint;
  688.   bytestoread:             (* number of bytes to read from the input file *)
  689.     word;
  690.   bytesread:               (* number of bytes successfully read from the *)
  691.     word;                  (*   input .snd file                          *)
  692.  
  693. begin (* convert_note *)
  694.     (* set output filename according to the note number *)
  695.   if note > 1 then
  696.     wavname := set_last( wavname, byte2char( note ) );
  697.  
  698.     (* attempt to open the output file for writing *)
  699.   assign( wavfile, wavname );
  700.   {$I-}
  701.   rewrite( wavfile, 1 );
  702.   {$I+}
  703.   open_successful := (IOResult = 0);
  704.  
  705.     (* if unsuccessful, display an error message and halt the program *)
  706.   if not open_successful then
  707.     begin
  708.     writeln;
  709.     writeln( 'Unable to create file ', wavname, '.' );
  710.     writeln( 'Either the name is not a valid filename, or the disk is ',
  711.       'write-protected, or' );
  712.     writeln( 'there is already a read-only file with that name.  Check the ',
  713.       'filename and' );
  714.     writeln( 'unprotect the disk, then try again.' );
  715.     close( sndfile );
  716.     halt( 1 );
  717.     end;
  718.  
  719.     (* copy the start and length of the note data from the notelist *)
  720.   notestart := notelist[note].start;
  721.   notelength := notelist[note].length;
  722.  
  723.     (* If the note length is zero, display a message and exit immediately
  724.        (proceeding to the next note).  The note length can be zero if an
  725.        instrument file was saved after a note was created but before it was 
  726.        recorded, or if a new sound file was saved before data was recorded 
  727.        into it. *)
  728.   if notelength = 0 then
  729.     begin
  730.     writeln;
  731.     writeln( 'Note number ', note, ' of the input .snd file contains ',
  732.       'no samples.  The .snd file was' );
  733.     writeln( 'saved after a note was created but before sound was recorded ',
  734.       'for the note, or' );
  735.     writeln( 'the .snd file is a new sound file with no data recorded into ',
  736.       'it.  This note is' );
  737.     writeln( 'being skipped.' );
  738.     close( wavfile );
  739.     erase( wavfile );
  740.     exit;
  741.     end; (* if notelength = 0 *)
  742.  
  743.     (* The following is to account for two bugs in earlier versions of 
  744.        Conv2snd.  When version 1.98 was written, the start field of the 
  745.        .snd header was not known, so Ken set it to zero.  He also had 
  746.        trouble getting the length right.  If the file was loaded into
  747.        Sound.pdm and resaved as Ken suggested, the problems would be fixed 
  748.        by Sound.pdm; the following is in case his advice was not heeded. *)
  749.   if (notestart = 0) and (note = 1) then
  750.     begin
  751.     notestart := 44;
  752.     if notelength > 255 then
  753.       notelength := filesize( sndfile ) - 44;
  754.     end; (* if notestart = 0 *)
  755.  
  756.     (* verify that the note data is in fact in the input file, i.e., verify 
  757.        that the start and length of the note data are valid *)
  758.   valid := (notestart >= 44) and (notelength > 0);
  759.   valid := valid and (notestart < filesize( sndfile )) and
  760.             (notelength < filesize( sndfile ));
  761.   valid := valid and (notestart+notelength <= filesize( sndfile ));
  762.  
  763.     (* if the note data is not valid, skip the note and return *)
  764.   if not valid then
  765.     begin
  766.     writeln;
  767.     writeln( 'The .snd header information for note number ', note,
  768.       ' of the input file is invalid.' );
  769.     writeln( 'The input .snd file is corrupt or has been truncated.  This ',
  770.       'note is being' );
  771.     writeln( 'skipped.' );
  772.     close( wavfile );
  773.     erase( wavfile );
  774.     exit;
  775.     end; (* if not valid *)
  776.  
  777.     (* announce the new file *)
  778.   writeln( '  Creating:  ', wavname );
  779.  
  780.     (* construct .wav header *)
  781.   stptr := @wavheader[0];             (* RIFF header *)
  782.   stptr^ := 'RIFF';
  783.   longptr := @wavheader[4];           (* length of RIFF data *)
  784.   longptr^ := notelength + 36;
  785.   stptr := @wavheader[8];             (* WAVE header *)
  786.   stptr^ := 'WAVE';
  787.   stptr := @wavheader[12];            (* format chunk label *)
  788.   stptr^ := 'fmt ';
  789.   longptr := @wavheader[16];          (* format chunk length *)
  790.   longptr^ := 16;
  791.   wordptr := @wavheader[20];          (* format type, 1 = Microsoft PCM *)
  792.   wordptr^ := 1;
  793.   wordptr := @wavheader[22];          (* number of channels, 1 = mono *)
  794.   wordptr^ := 1;
  795.   longptr := @wavheader[24];          (* sampling rate, samples per second *)
  796.   longptr^ := longint( rate );
  797.   longptr := @wavheader[28];          (* data rate, bytes per second *)
  798.   longptr^ := longint( rate );
  799.   wordptr := @wavheader[32];          (* bytes per (multichannel) sample *)
  800.   wordptr^ := 1;
  801.   wordptr := @wavheader[34];          (* bits per sample *)
  802.   wordptr^ := 8;
  803.   stptr := @wavheader[36];            (* data chunk label *)
  804.   stptr^ := 'data';
  805.   longptr := @wavheader[40];          (* data chunk length *)
  806.   longptr^ := notelength;
  807.  
  808.     (* write .wav header to output file *)
  809.   blockwrite( wavfile, wavheader, 44, byteswritten );
  810.  
  811.     (* check for full disk *)
  812.   if byteswritten <> 44 then
  813.     begin
  814.     writeln;
  815.     writeln( 'The disk where ', wavname );
  816.     writeln( '... is to be written is full.  Try again and specify ',
  817.       'another disk for the' );
  818.     writeln( 'output .wav file(s).' );
  819.     close( sndfile );
  820.     close( wavfile );
  821.     erase( wavfile );
  822.     halt( 1 );
  823.     end; (* if byteswritten <> 44 *)
  824.  
  825.     (* loop over the sound data and copy it to the output file *)
  826.   seek( sndfile, notestart );
  827.   bytesleft := notelength;
  828.   while bytesleft > 0 do
  829.     begin
  830.       (* determine amount of data to copy this pass *)
  831.     if bytesleft > bufsize then
  832.       bytestoread := bufsize
  833.     else
  834.       bytestoread := bytesleft;
  835.       (* read in a buffer of sound data *)
  836.     blockread( sndfile, buffer, bytestoread, bytesread );
  837.       (* if there was an error while reading, halt the program with a 
  838.          message *)
  839.     if bytesread <> bytestoread then
  840.       begin
  841.       writeln;
  842.       writeln( 'Error reading the input .snd file.  The disk structure may ',
  843.         'be corrupt.  Use' );
  844.       writeln( 'chkdsk to verify the disk structure.' );
  845.       close( sndfile );
  846.       close( wavfile );
  847.       erase( wavfile );
  848.       halt( 1 );
  849.       end; (* if bytesread <> bytestoread *)
  850.       (* write out the buffer to the .wav file *)
  851.     blockwrite( wavfile, buffer, bytesread, byteswritten );
  852.       (* if the disk is full, halt the program with an error message *)
  853.     if byteswritten <> bytesread then
  854.       begin
  855.       writeln;
  856.       writeln( 'The disk where ', wavname );
  857.       writeln( '... is to be written is full.  Try again and specify ',
  858.         'another disk for the' );
  859.       writeln( 'output .wav file(s).' );
  860.       close( sndfile );
  861.       close( wavfile );
  862.       erase( wavfile );
  863.       halt( 1 );
  864.       end; (* if byteswritten <> bytesread *)
  865.       (* update count of bytes left to copy *)
  866.     bytesleft := bytesleft - byteswritten;
  867.     end; (* while bytesleft > 0 *)
  868.  
  869.     (* close the output file *)
  870.   close( wavfile );
  871. end; (* convert_note *)
  872.  
  873. (*********************************************************************)
  874.  
  875. procedure display_exit;
  876.   (* This procedure displays an exit banner on successful conversion. *)
  877.  
  878. begin (* display_exit *)
  879.   writeln( 'Conversion complete.  Thank you for using Snd2wav.' );
  880. end; (* display_exit *)
  881.  
  882. (*********************************************************************)
  883.  
  884. begin (* snd2wav *)
  885.   display_intro;
  886.   get_filenames( sndname, wavname );
  887.   if is_newsnd( sndname ) then
  888.     read_newheader( sndname, sndfile, rate, numnotes, notelist )
  889.   else
  890.     read_sndheader( sndname, sndfile, rate, numnotes, notelist );
  891.   writeln( 'Converting:  ', sndname );
  892.   for note := 1 to numnotes do
  893.     convert_note( sndfile, wavname, rate, note, notelist );
  894.   close( sndfile );
  895.   writeln;
  896.   display_exit;
  897.   halt( 0 );               (* errorlevel 0 = success *)
  898. end. (* snd2wav *)